[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
この記事では、Scala 3(コードネーム:Dotty)で新たに導入された「合併型(Union Types)」について解説します。
合併型とは「A
またはB
」を表す型
合併型とは、二つの型の性質のいずれかを満たすような値のもつ型を表します。
|
記号で表します。
例えばA
という型とB
という型があるとき、A | B
と表すと「A
である、もしくはB
である型」という意味になります。
例えば以下のようなコードを書くことができます。
case class UserId(value: Int) extends AnyVal case class Email(value: String) extends AnyVal case class User() def findUser(id: UserId | Email): User = { id match { case UserId(value) => lookupById(value) case Email(value) => lookupByEmail(value) } }
合併型は可換的で結合的
合併型は可換的です。
つまり、A | B
は B | A
と同じ型となります。
また、結合的でもあります。
つまり、A | (B | C)
は (A | B) | C
と同じ型となります。
合併型は交差型と組み合わせると分配的
交差型は&
記号を使用し、例えば型A
と型B
について「A
かつB
」といった関係性を表現します。
合併型は交差型と組み合わせることにより、分配的になります。
A & (B | C)
は、A & B | A & C
と同じ型となります。
計算の優先順位は&
の方が高く、|
の方が低いので、このように表記することができます。
合併型は交差型と対をなす概念なので、交差型についても合わせて押さえておくと良いでしょう。
内部構造までは引き継がない
以下のコードはコンパイルが通りません。
というのも、A | B
にはdef run: Result
というメソッドを持たないからです。
trait A { def run: Result } trait B { def run: Result } def execute(x: A | B) = x.run
たとえ両者に共通するメンバが存在していたとしても、合併した型にこれが引き継がれることはないということです。
これに対して、以下のコンパイルは通ります。
trait C { def run: Result } trait A extends C trait B extends C def execute(x: A | B) = x.run
A
とB
はいずれもC
のサブクラスなので、A | B
もC
のサブクラスです。
したがってrun
メソッドを持っている、というわけです。
備考
本記事では"union types"に対する訳語として「合併型」を採用しています。
「直和型」「合併型」の違いについては以下のリンクを参考としています。